// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/events/ozone/evdev/event_device_test_util.h"

#include <stdint.h>

#include "base/format_macros.h"
#include "base/macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "ui/events/ozone/evdev/event_device_info.h"
#include "ui/events/ozone/evdev/event_device_util.h"

namespace ui {

namespace {

    // This test requres 64 bit groups in bitmask inputs (merge them if 32-bit).
    const int kTestDataWordSize = 64;

#define EVDEV_BITS_TO_GROUPS(x) \
    (((x) + kTestDataWordSize - 1) / kTestDataWordSize)

    std::string SerializeBitfield(unsigned long* bitmap, int max)
    {
        std::string ret;

        for (int i = EVDEV_BITS_TO_GROUPS(max) - 1; i >= 0; i--) {
            if (bitmap[i] || ret.size()) {
                base::StringAppendF(&ret, "%lx", bitmap[i]);

                if (i > 0)
                    ret += " ";
            }
        }

        if (ret.length() == 0)
            ret = "0";

        return ret;
    }

    bool ParseBitfield(const std::string& bitfield,
        size_t max_bits,
        std::vector<unsigned long>* out)
    {
        std::vector<std::string> groups = base::SplitString(
            bitfield, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);

        out->resize(EVDEV_BITS_TO_LONGS(max_bits));

        // Convert big endian 64-bit groups to little endian EVDEV_LONG_BIT groups.
        for (size_t i = 0; i < groups.size(); ++i) {
            int off = groups.size() - 1 - i;

            uint64_t val;
            if (!base::HexStringToUInt64(groups[off], &val))
                return false;

            for (int j = 0; j < kTestDataWordSize; ++j) {
                unsigned int code = i * kTestDataWordSize + j;

                if (code >= max_bits)
                    break;

                if (val & (1UL << j))
                    EvdevSetBit(&(*out)[0], code);
            }
        }

        // Require canonically formatted input.
        if (bitfield != SerializeBitfield(out->data(), max_bits))
            return false;

        return true;
    }

} // namespace

// Captured from Chromebook Pixel.
const DeviceCapabilities kLinkKeyboard = {
    /* path */ "/sys/devices/platform/i8042/serio0/input/input6/event6",
    /* name */ "AT Translated Set 2 keyboard",
    /* phys */ "isa0060/serio0/input0",
    /* uniq */ "",
    /* bustype */ "0011",
    /* vendor */ "0001",
    /* product */ "0001",
    /* version */ "ab83",
    /* prop */ "0",
    /* ev */ "120013",
    /* key */ "400402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe",
    /* rel */ "0",
    /* abs */ "0",
    /* msc */ "10",
    /* sw */ "0",
    /* led */ "7",
    /* ff */ "0",
};

// Captured from Chromebook Pixel.
const DeviceAbsoluteAxis kLinkTouchscreenAbsAxes[] = {
    { ABS_X, { 0, 0, 2559, 0, 0, 20 } },
    { ABS_Y, { 0, 0, 1699, 0, 0, 20 } },
    { ABS_PRESSURE, { 0, 0, 255, 0, 0, 0 } },
    { ABS_MT_SLOT, { 0, 0, 15, 0, 0, 0 } },
    { ABS_MT_TOUCH_MAJOR, { 0, 0, 938, 0, 0, 0 } },
    { ABS_MT_ORIENTATION, { 0, -3, 4, 0, 0, 0 } },
    { ABS_MT_POSITION_X, { 0, 0, 2559, 0, 0, 20 } },
    { ABS_MT_POSITION_Y, { 0, 0, 1699, 0, 0, 20 } },
    { ABS_MT_TRACKING_ID, { 0, 0, 65535, 0, 0, 0 } },
    { ABS_MT_PRESSURE, { 0, 0, 255, 0, 0, 0 } },
};
const DeviceCapabilities kLinkTouchscreen = {
    /* path */ "/sys/devices/pci0000:00/0000:00:02.0/i2c-2/2-004a/"
               "input/input7/event7",
    /* name */ "Atmel maXTouch Touchscreen",
    /* phys */ "i2c-2-004a/input0",
    /* uniq */ "",
    /* bustype */ "0018",
    /* vendor */ "0000",
    /* product */ "0000",
    /* version */ "0000",
    /* prop */ "0",
    /* ev */ "b",
    /* key */ "400 0 0 0 0 0",
    /* rel */ "0",
    /* abs */ "671800001000003",
    /* msc */ "0",
    /* sw */ "0",
    /* led */ "0",
    /* ff */ "0",
    kLinkTouchscreenAbsAxes,
    arraysize(kLinkTouchscreenAbsAxes),
};

// Captured from Chromebook Pixel.
const DeviceAbsoluteAxis kLinkTouchpadAbsAxes[] = {
    { ABS_X, { 0, 0, 2040, 0, 0, 20 } },
    { ABS_Y, { 0, 0, 1360, 0, 0, 20 } },
    { ABS_PRESSURE, { 0, 0, 255, 0, 0, 0 } },
    { ABS_MT_SLOT, { 0, 0, 9, 0, 0, 0 } },
    { ABS_MT_TOUCH_MAJOR, { 0, 0, 1878, 0, 0, 0 } },
    { ABS_MT_ORIENTATION, { 0, -3, 4, 0, 0, 0 } },
    { ABS_MT_POSITION_X, { 0, 0, 2040, 0, 0, 20 } },
    { ABS_MT_POSITION_Y, { 0, 0, 1360, 0, 0, 20 } },
    { ABS_MT_TRACKING_ID, { 0, 0, 65535, 0, 0, 0 } },
    { ABS_MT_PRESSURE, { 0, 0, 255, 0, 0, 0 } },
};
const DeviceCapabilities kLinkTouchpad = {
    /* path */ "/sys/devices/pci0000:00/0000:00:02.0/i2c-1/1-004b/"
               "input/input8/event8",
    /* name */ "Atmel maXTouch Touchpad",
    /* phys */ "i2c-1-004b/input0",
    /* uniq */ "",
    /* bustype */ "0018",
    /* vendor */ "0000",
    /* product */ "0000",
    /* version */ "0000",
    /* prop */ "5",
    /* ev */ "b",
    /* key */ "e520 10000 0 0 0 0",
    /* rel */ "0",
    /* abs */ "671800001000003",
    /* msc */ "0",
    /* sw */ "0",
    /* led */ "0",
    /* ff */ "0",
    kLinkTouchpadAbsAxes,
    arraysize(kLinkTouchpadAbsAxes),
};

// Captured from generic HP KU-1156 USB keyboard.
const DeviceCapabilities kHpUsbKeyboard = {
    /* path */ "/sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.3/2-1.3:1.0/"
               "input/input17/event10",
    /* name */ "Chicony HP Elite USB Keyboard",
    /* phys */ "usb-0000:00:1d.0-1.3/input0",
    /* uniq */ "",
    /* bustype */ "0003",
    /* vendor */ "03f0",
    /* product */ "034a",
    /* version */ "0110",
    /* prop */ "0",
    /* ev */ "120013",
    /* key */ "1000000000007 ff9f207ac14057ff febeffdfffefffff "
              "fffffffffffffffe",
    /* rel */ "0",
    /* abs */ "0",
    /* msc */ "10",
    /* sw */ "0",
    /* led */ "7",
    /* ff */ "0",
};

// Captured from generic HP KU-1156 USB keyboard (2nd device with media keys).
const DeviceAbsoluteAxis kHpUsbKeyboard_ExtraAbsAxes[] = {
    { ABS_VOLUME, { 0, 0, 767, 0, 0, 0 } },
};
const DeviceCapabilities kHpUsbKeyboard_Extra = {
    /* path */ "/sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.3/2-1.3:1.1/"
               "input/input18/event16",
    /* name */ "Chicony HP Elite USB Keyboard",
    /* phys */ "usb-0000:00:1d.0-1.3/input1",
    /* uniq */ "",
    /* bustype */ "0003",
    /* vendor */ "03f0",
    /* product */ "034a",
    /* version */ "0110",
    /* prop */ "0",
    /* ev */ "1f",
    /* key */ "3007f 0 0 483ffff17aff32d bf54444600000000 1 120f938b17c000 "
              "677bfad941dfed 9ed68000004400 10000002",
    /* rel */ "40",
    /* abs */ "100000000",
    /* msc */ "10",
    /* sw */ "0",
    /* led */ "0",
    /* ff */ "0",
    kHpUsbKeyboard_ExtraAbsAxes,
    arraysize(kHpUsbKeyboard_ExtraAbsAxes),
};

// Captured from Dell MS111-L 3-Button Optical USB Mouse.
const DeviceCapabilities kLogitechUsbMouse = {
    /* path */ "/sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.4/"
               "2-1.2.4:1.0/input/input16/event9",
    /* name */ "Logitech USB Optical Mouse",
    /* phys */ "usb-0000:00:1d.0-1.2.4/input0",
    /* uniq */ "",
    /* bustype */ "0003",
    /* vendor */ "046d",
    /* product */ "c05a",
    /* version */ "0111",
    /* prop */ "0",
    /* ev */ "17",
    /* key */ "ff0000 0 0 0 0",
    /* rel */ "143",
    /* abs */ "0",
    /* msc */ "10",
    /* sw */ "0",
    /* led */ "0",
    /* ff */ "0",
};

// Captured from "Mimo Touch 2" Universal DisplayLink monitor.
const DeviceAbsoluteAxis kMimoTouch2TouchscreenAbsAxes[] = {
    { ABS_X, { 0, 0, 2047, 0, 0, 0 } },
    { ABS_Y, { 0, 0, 2047, 0, 0, 0 } },
};
const DeviceCapabilities kMimoTouch2Touchscreen = {
    /* path */ "/sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.3/2-1.3.2/"
               "2-1.3.2:1.0/input/input15/event14",
    /* name */ "eGalax Inc. USB TouchController",
    /* phys */ "usb-0000:00:1d.0-1.3.2/input0",
    /* uniq */ "",
    /* bustype */ "0003",
    /* vendor */ "0eef",
    /* product */ "0001",
    /* version */ "0100",
    /* prop */ "0",
    /* ev */ "b",
    /* key */ "400 0 0 0 0 0",
    /* rel */ "0",
    /* abs */ "3",
    /* msc */ "0",
    /* sw */ "0",
    /* led */ "0",
    /* ff */ "0",
    kMimoTouch2TouchscreenAbsAxes,
    arraysize(kMimoTouch2TouchscreenAbsAxes),
};

// Captured from Wacom Intuos Pen and Touch Small Tablet.
const DeviceAbsoluteAxis kWacomIntuosPtS_PenAbsAxes[] = {
    { ABS_X, { 0, 0, 15200, 4, 0, 100 } },
    { ABS_Y, { 0, 0, 9500, 4, 0, 100 } },
    { ABS_PRESSURE, { 0, 0, 1023, 0, 0, 0 } },
    { ABS_DISTANCE, { 0, 0, 31, 0, 0, 0 } },
};
const DeviceCapabilities kWacomIntuosPtS_Pen = {
    /* path */ "/sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.3/"
               "2-1.2.3:1.0/input/input9/event9",
    /* name */ "Wacom Intuos PT S Pen",
    /* phys */ "",
    /* uniq */ "",
    /* bustype */ "0003",
    /* vendor */ "056a",
    /* product */ "0302",
    /* version */ "0100",
    /* prop */ "1",
    /* ev */ "b",
    /* key */ "1c03 0 0 0 0 0",
    /* rel */ "0",
    /* abs */ "3000003",
    /* msc */ "0",
    /* sw */ "0",
    /* led */ "0",
    /* ff */ "0",
    kWacomIntuosPtS_PenAbsAxes,
    arraysize(kWacomIntuosPtS_PenAbsAxes),
};

// Captured from Wacom Intuos Pen and Touch Small Tablet.
const DeviceAbsoluteAxis kWacomIntuosPtS_FingerAbsAxes[] = {
    { ABS_X, { 0, 0, 4096, 4, 0, 26 } },
    { ABS_Y, { 0, 0, 4096, 4, 0, 43 } },
    { ABS_MT_SLOT, { 0, 0, 15, 0, 0, 0 } },
    { ABS_MT_TOUCH_MAJOR, { 0, 0, 4096, 0, 0, 0 } },
    { ABS_MT_TOUCH_MINOR, { 0, 0, 4096, 0, 0, 0 } },
    { ABS_MT_POSITION_X, { 0, 0, 4096, 4, 0, 26 } },
    { ABS_MT_POSITION_Y, { 0, 0, 4096, 4, 0, 43 } },
    { ABS_MT_TRACKING_ID, { 0, 0, 65535, 0, 0, 0 } },
};
const DeviceCapabilities kWacomIntuosPtS_Finger = {
    /* path */ "/sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.3/"
               "2-1.2.3:1.1/input/input10/event10",
    /* name */ "Wacom Intuos PT S Finger",
    /* phys */ "",
    /* uniq */ "",
    /* bustype */ "0003",
    /* vendor */ "056a",
    /* product */ "0302",
    /* version */ "0100",
    /* prop */ "1",
    /* ev */ "2b",
    /* key */ "e520 630000 0 0 0 0",
    /* rel */ "0",
    /* abs */ "263800000000003",
    /* msc */ "0",
    /* sw */ "4000",
    /* led */ "0",
    /* ff */ "0",
    kWacomIntuosPtS_FingerAbsAxes,
    arraysize(kWacomIntuosPtS_FingerAbsAxes),
};

// Captured from Logitech Wireless Touch Keyboard K400.
const DeviceAbsoluteAxis kLogitechTouchKeyboardK400AbsAxes[] = {
    { ABS_VOLUME, { 0, 1, 652, 0, 0, 0 } },
};
const DeviceCapabilities kLogitechTouchKeyboardK400 = {
    /* path */ "/sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.3/"
               "2-1.2.3:1.2/0003:046D:C52B.0006/input/input19/event17",
    /* name */ "Logitech Unifying Device. Wireless PID:4024",
    /* phys */ "usb-0000:00:1d.0-1.2.3:1",
    /* uniq */ "",
    /* bustype */ "001d",
    /* vendor */ "046d",
    /* product */ "4024",
    /* version */ "0111",
    /* prop */ "0",
    /* ev */ "12001f",
    /* key */ "3007f 0 0 483ffff17aff32d bf54444600000000 ffff0001 "
              "130f938b17c007 ffff7bfad9415fff febeffdfffefffff "
              "fffffffffffffffe",
    /* rel */ "1c3",
    /* abs */ "100000000",
    /* msc */ "10",
    /* sw */ "0",
    /* led */ "1f",
    /* ff */ "0",
    kLogitechTouchKeyboardK400AbsAxes,
    arraysize(kLogitechTouchKeyboardK400AbsAxes),
};

// Captured from Elo TouchSystems 2700 touchscreen.
const DeviceAbsoluteAxis kElo_TouchSystems_2700AbsAxes[] = {
    { ABS_X, { 0, 0, 4095, 0, 0, 0 } },
    { ABS_Y, { 0, 0, 4095, 0, 0, 0 } },
    { ABS_MISC, { 0, 0, 256, 0, 0, 0 } },
};
const DeviceCapabilities kElo_TouchSystems_2700 = {
    /* path */ "/sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.3/2-1.3:1.0/"
               "input/input9/event9",
    /* name */ "Elo TouchSystems, Inc. Elo TouchSystems 2700 IntelliTouch(r) "
               "USB Touchmonitor Interface",
    /* phys */ "usb-0000:00:1d.0-1.3/input0",
    /* uniq */ "20A01347",
    /* bustype */ "0003",
    /* vendor */ "04e7",
    /* product */ "0020",
    /* version */ "0100",
    /* prop */ "0",
    /* ev */ "1b",
    /* key */ "10000 0 0 0 0",
    /* rel */ "0",
    /* abs */ "10000000003",
    /* msc */ "10",
    /* sw */ "0",
    /* led */ "0",
    /* ff */ "0",
    kElo_TouchSystems_2700AbsAxes,
    arraysize(kElo_TouchSystems_2700AbsAxes),
};

// Captured from Intel reference design: "Wilson Beach".
const DeviceAbsoluteAxis kWilsonBeachActiveStylusAbsAxes[] = {
    { ABS_X, { 0, 0, 9600, 0, 0, 33 } },
    { ABS_Y, { 0, 0, 7200, 0, 0, 44 } },
    { ABS_PRESSURE, { 0, 0, 1024, 0, 0, 0 } },
};
const DeviceCapabilities kWilsonBeachActiveStylus = {
    /* path */ "/sys/devices/pci0000:00/INT3433:00/i2c-1/"
               "i2c-NTRG0001:00/0018:1B96:0D03.0004/input/"
               "input11/event10",
    /* name */ "NTRG0001:00 1B96:0D03 Pen",
    /* phys */ "",
    /* uniq */ "",
    /* bustype */ "0018",
    /* vendor */ "1b96",
    /* product */ "0d03",
    /* version */ "0100",
    /* prop */ "0",
    /* ev */ "1b",
    /* key */ "c03 1 0 0 0 0",
    /* rel */ "0",
    /* abs */ "1000003",
    /* msc */ "10",
    /* sw */ "0",
    /* led */ "0",
    /* ff */ "0",
    kWilsonBeachActiveStylusAbsAxes,
    arraysize(kWilsonBeachActiveStylusAbsAxes),
};

ui::InputDeviceType InputDeviceTypeFromBusType(int bustype)
{
    switch (bustype) {
    case BUS_I8042:
    case BUS_I2C:
        return ui::InputDeviceType::INPUT_DEVICE_INTERNAL;
    case BUS_USB:
    case 0x1D: // Used in kLogitechTouchKeyboardK400 but not listed in input.h.
        return ui::InputDeviceType::INPUT_DEVICE_EXTERNAL;
    default:
        NOTREACHED() << "Unexpected bus type";
        return ui::InputDeviceType::INPUT_DEVICE_UNKNOWN;
    }
}

bool CapabilitiesToDeviceInfo(const DeviceCapabilities& capabilities,
    EventDeviceInfo* devinfo)
{
    std::vector<unsigned long> ev_bits;
    if (!ParseBitfield(capabilities.ev, EV_CNT, &ev_bits))
        return false;
    devinfo->SetEventTypes(&ev_bits[0], ev_bits.size());

    std::vector<unsigned long> key_bits;
    if (!ParseBitfield(capabilities.key, KEY_CNT, &key_bits))
        return false;
    devinfo->SetKeyEvents(&key_bits[0], key_bits.size());

    std::vector<unsigned long> rel_bits;
    if (!ParseBitfield(capabilities.rel, REL_CNT, &rel_bits))
        return false;
    devinfo->SetRelEvents(&rel_bits[0], rel_bits.size());

    std::vector<unsigned long> abs_bits;
    if (!ParseBitfield(capabilities.abs, ABS_CNT, &abs_bits))
        return false;
    devinfo->SetAbsEvents(&abs_bits[0], abs_bits.size());

    std::vector<unsigned long> msc_bits;
    if (!ParseBitfield(capabilities.msc, MSC_CNT, &msc_bits))
        return false;
    devinfo->SetMscEvents(&msc_bits[0], msc_bits.size());

    std::vector<unsigned long> led_bits;
    if (!ParseBitfield(capabilities.led, LED_CNT, &led_bits))
        return false;
    devinfo->SetLedEvents(&led_bits[0], led_bits.size());

    std::vector<unsigned long> prop_bits;
    if (!ParseBitfield(capabilities.prop, INPUT_PROP_CNT, &prop_bits))
        return false;
    devinfo->SetProps(&prop_bits[0], prop_bits.size());

    for (size_t i = 0; i < capabilities.abs_axis_count; ++i) {
        const DeviceAbsoluteAxis& axis = capabilities.abs_axis[i];
        devinfo->SetAbsInfo(axis.code, axis.absinfo);
    }

    size_t slots = devinfo->GetAbsMtSlotCount();
    std::vector<int32_t> zero_slots(slots, 0);
    std::vector<int32_t> minus_one_slots(slots, -1);
    for (int code = EVDEV_ABS_MT_FIRST; code <= EVDEV_ABS_MT_LAST; ++code) {
        if (!devinfo->HasAbsEvent(code))
            continue;
        if (code == ABS_MT_TRACKING_ID)
            devinfo->SetAbsMtSlots(code, minus_one_slots);
        else
            devinfo->SetAbsMtSlots(code, zero_slots);
    }

    int bustype = 0;
    sscanf(capabilities.bustype, "%x", &bustype);
    devinfo->SetDeviceType(InputDeviceTypeFromBusType(bustype));

    return true;
}

} // namespace ui
